package org.tau.workshop2011.codegen

import org.tau.workshop2011.expressions
import expressions.Type
import org.tau.workshop2011.parsing.AST._
import org.tau.workshop2011.semantics.SemanticAnalysis.WidgetAnalysis
import java.lang.AssertionError
import org.tau.workshop2011.semantics.{SemanticAnalysis, LinearExpansion, ResolvedVariable, ReadOnlyStatus}
import collection.mutable.{Stack, HashMap => MutableHashMap}
import java.io.FileWriter

object CodeGenerator {

  def globalVarName(node: Variable): String = {
    node.environment.get(node).globalName
  }

  def runFuncName(widgetName: String) = "run_" + widgetName

  val inputPropsName = "initialValues"
  val inputPropsType = "Map[String, _]"

  val propsMapName = "AllProps"
  val propsMapType = "scala.collection.mutable.LinkedHashSet[Property[_]]"

  val exposedPropsMapName = "ExposedProps"
  val exposedPropsInternalMapType = "scala.collection.mutable.HashMap[String, Property[_]]"
  val exposedPropsMapType = "Map[String, Property[_]]"

  val UpdateManagerName = "UM"
  val UpdateManagerType = "UpdateManager"

  def printFunctionHeader(cg: CodeGeneratorVisitor) {
    cg.cPrintln("def ",runFuncName(cg.widgetName),"(",inputPropsName,":",inputPropsType,"): (JComponent, ", exposedPropsMapType ,")= {")
    cg.cPrintln("import org.tau.workshop2011.expressions._")
    cg.cPrintln("import org.tau.workshop2011.widgets._")
    cg.cPrintln()
    cg.cPrintln("/* A set of all the properties.")
    cg.cPrintln(" * This is needed to allow the user to programmatically set properties")
    cg.cPrintln(" */")
    cg.cPrintln("val ",propsMapName,":",propsMapType," = new ",propsMapType)
    cg.cPrintln("val ",exposedPropsMapName,":",exposedPropsInternalMapType," = new ",exposedPropsInternalMapType)
    cg.cPrintln("val ",UpdateManagerName,":",UpdateManagerType," = new ",UpdateManagerType)
    cg.cPrintln("")
  }

  def printFunctionFooter(cg: CodeGeneratorVisitor) {
    var widget = cg.wa.rootWidget
    while (widget.isInstanceOf[PropertyScope])
      widget = widget.asInstanceOf[PropertyScope].widget

    cg.cPrintln("(",cg.getRootWidgetName, ".getNativeWidget, ", exposedPropsMapName, ".toMap)")
    cg.cPrintln("}")
  }

  def genCode(widgetName: String, wa: WidgetAnalysis, CP: CodePrinter) {
    val cg = new CodeGeneratorVisitor(widgetName, wa, CP)

    /* Print documentation on which properties should be specified */
    CP.cPrintln("// Properties that may be specified are:")
    for (prop <- wa.dependencies.dependencySortedVars)
      if (prop.definition.isInstanceOf[InitialAttribute]) {
        val P = prop.definition.asInstanceOf[InitialAttribute]
        CP.cPrintln("// - `", P.getName, "` of type ", P.getLeftType)
        if (P.value == None) {
          CP.cPrintln("//   Must be specified!")
        } else {
          CP.cPrint("//   Initialized by default to "); P.value.get.accept(cg);CP.cPrintln()
        }
      }
    CP.cPrintln("// You can access these properties via the returned Map")

    /* Let's begin generating the actual code */
    printFunctionHeader(cg)

    /* Now, let's declare all the properties */
    for (prop <- wa.dependencies.dependencySortedVars) {
      CP.cPrintln("/* Code for local property ", prop.definition.getName, " (which got the unique ID ",prop.globalName,") */")

      /* Print the dependencies of this property */
      if (wa.dependencies.apply(prop).isEmpty) {
        CP.cPrintln("/* This property has no dependencies */")
      } else {
        CP.cPrintln("/* The direct and indirect dependencies of this property are:")
        CP.cPrint(" * ")
        CP.cPrint(wa.dependencies(prop).toList map {(prop) => globalVarName(prop.getVariable)} mkString ", ")
        CP.cPrintln()
        CP.cPrintln(" */")
      }
      
      /* Warn if this property is read-only or not */
      if (wa.readOnlyProps contains prop)
        CP.cPrintln("/* This property is READ ONLY! */")

      /* Generate the code for this property */
      prop.definition accept cg

      /* Register this property in the global property map */
      CP.cPrint(exposedPropsMapName," += ")
      CP.cPrintStringLiteral(prop.getVariable.id)
      CP.cPrintln(" ->", prop.globalName)

      /* Register this property in the list of all properties */
      CP.cPrintln(propsMapName," += ",prop.globalName)

      CP.cPrintln()
    }

    /* Generate all the widgets */
    wa.rootWidget accept cg

    /* Finish it all */
    printFunctionFooter(cg)
  }

  def genCode(sa: SemanticAnalysis, className: String = "GuiProgram", packageName: String = "org.tau.workshop2011.showcase", destFile: String = null) {
    val CP = new CodePrinter

    if (! (packageName.isEmpty))
      CP.cPrintln("package ", packageName)
    CP.cPrintln("import javax.swing.JComponent")
    CP.cPrintln("import org.tau.workshop2011.codegen._")
    CP.cPrintln("object ",className," extends WidgetGenerator {")
    
    for ((name,wa) <- sa.analysis) {
      genCode(name,wa,CP)
      if (name == "main_window") {
        CP.cPrintln("// You may need to modify the code below to plugn in")
        CP.cPrintln("// values to other mandatory properties")
        CP.cPrintln("def TestMainWindow(width: Int = 800, height: Int = 600) {")
          CP.cPrintln("import javax.swing.{JComponent, JFrame}")
          CP.cPrintln("val f = new JFrame()")
          CP.cPrintln()
          CP.cPrintln("var input: Map[String, Any] = Map()")
          CP.cPrintln("input += \"width\" -> width")
          CP.cPrintln("input += \"height\" -> height")
          CP.cPrintln()
          CP.cPrintln("val pair = ",runFuncName(name),"(input)")
          CP.cPrintln("val MainComp = pair._1")
          CP.cPrintln("val Props = pair._2")
          CP.cPrintln()
          CP.cPrintln("f.add(MainComp)")
          CP.cPrintln("f.pack()")
          CP.cPrintln("f.setVisible(true)")
          CP.cPrintln("f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)")
        CP.cPrintln("}")
      }
      CP.cPrintln()
    }

    /* Now implement the widget generator interface */
    CP.cPrintln("def GenGui(name: String, props: Map[String, _]): (JComponent, Map[String, Property[_]]) = {")
      CP.cPrintln("name match {")
        for ((name,wa) <- sa.analysis) {
          CP.cPrintln("case \"",name,"\" => ",runFuncName(name),"(props)")
        }
        CP.cPrintln("""case _ => throw new IllegalArgumentException("No top level widget defined for the name " + name)""")
      CP.cPrintln("}")
    CP.cPrintln("}")

    /* Finish the object generation */
    CP.cPrintln("}")

    val code = CP.callOnCodegenEnd

    /* Output */
    if (destFile == null || destFile == "")
      println(code)
    else {
      val file = new FileWriter(destFile)
      file.write(code)
      file.close()
    }
  }
}

class CodePrinter {
  val currentLine:StringBuilder = new StringBuilder
  val codeBuffer:StringBuilder = new StringBuilder
  var indentLevel = 0

  def cPrint (text: Any*) {
    for (part <- text) {
      val str = String valueOf part
      currentLine append str
      for (char <- str) {
        if (char == '{') {
          indentLevel += 1
        } else if (char == '}' && indentLevel > 0) {
          /* A fancy way to make sure we don't make the indentation negative */
          indentLevel -= 1
        }
      }
    }
  }

  def commitLine() {
    val line = currentLine.toString()
    val indent = if (line matches """.*\{\s*""") indentLevel - 1 else indentLevel
    if (! (currentLine isEmpty))
      for (i <- 0 until indent)
        codeBuffer append "  "
    codeBuffer append line
    codeBuffer append "\n"
    currentLine.clear()
  }
  /* Similar to the regular cPrint, but does not add indentation based on the
   * content, and adds quotes around the printed text
   */
  def cPrintStringLiteral (text: String) {
    currentLine append '"' append text append '"'
  }

  def cPrintln (text: Any*) {
    cPrint(text: _*)
    commitLine()
  }

  def callOnCodegenEnd: String = {
    if (! (currentLine isEmpty))
      commitLine()
    codeBuffer.toString()
  }

}

class CodeGeneratorVisitor(val widgetName: String, val wa:WidgetAnalysis, val codePrinter: CodePrinter) extends ASTDeepVisitor {

  import CodeGenerator._

  /* Parenthesized expressions */
  case class ParenthesizedExpression(node: Expr) {
    def printExprCode() {
      cPrint("(")
      node accept CodeGeneratorVisitor.this
      cPrint(")")
    }
  }

  implicit def Expr2Parens(node: Expr) = new ParenthesizedExpression(node)

  def cPrint (text: Any*) {
    codePrinter.cPrint(text: _ *)
  }
  def cPrintln (text: Any*) {
    codePrinter.cPrintln(text: _ *)
  }
  def cPrintStringLiteral (text: String) {
    codePrinter.cPrintStringLiteral(text)
  }

  def cPrintList(elems: List[Expr], seperator: String, ifEmpty: String) {
    if (elems.isEmpty)
      cPrint(ifEmpty)
    else {
      elems.head.printExprCode()
      for (e <- elems.tail) {
        cPrint (seperator)
        e.printExprCode()
      }
    }
  }

  var widgetPath: Stack[Int] = Stack(0)

  def startWidget() {
    widgetPath push (widgetPath.pop() + 1)
  }
  def startChildren() {
    widgetPath push (0)
  }
  def finishChildren() {
    widgetPath.pop()
  }
  def finishWidget() {

  }

  def getCurrentWidgetName: String = {
    var suffix = widgetPath.head.toString
    for (elem <- widgetPath.tail)
      suffix = elem + "_" + suffix
    "widget_" + suffix
  }
  def getParentWidgetName: String = {
    var suffix = widgetPath.tail.head.toString
    for (elem <- widgetPath.tail.tail)
      suffix = elem + "_" + suffix
    "widget_" + suffix
  }
  def getRootWidgetName: String = {
    "widget_1"
  }

  def PrintAttrNameMapping(node: Attribute) {
    cPrint('"', node.getName, '"', "->", globalVarName(node.leftSide))
  }

  override def visit(node: AtomicWidget): Any = {
    startWidget()
    val prefix = getCurrentWidgetName
    cPrint("val ",prefix,"_props: Map[String, DSLProperty[_]] = Map(")
    
    if (! node.attributes.isEmpty) {
      PrintAttrNameMapping(node.attributes.head)
      for (attr <- node.attributes.tail) {
        cPrint(", ")
        PrintAttrNameMapping(attr)
      }
    }
    cPrintln(")")

    cPrint("val ",prefix,"_width: Option[DSLProperty[Int]] = ")
    node.width match {
      case Some(realExpr) => cPrint("Some(new InitialProperty[Int]("); realExpr.printExprCode(); cPrint(", ", UpdateManagerName, "))")
      case None => cPrint("None")
    }
    cPrintln()
    cPrint("val ",prefix,"_height: Option[DSLProperty[Int]] = ")
    node.height match {
      case Some(realExpr) => cPrint("Some(new InitialProperty[Int]("); realExpr.printExprCode(); cPrint(", ", UpdateManagerName, "))")
      case None => cPrint("None")
    }
    cPrintln()
    cPrint("val ",prefix," = NativeWidgetMaker.")

    node.kind match {
      case "label" => cPrint("MakeLabel")
      case "textbox" => cPrint("MakeTextBox")
      case "button" => cPrint("MakeButton")
      case "checkbox" => cPrint("MakeCheckBox")
      case "radio" => cPrint("MakeRadioButton")
      case "image" => cPrint("MakeImage")
      case "comboxbox" => cPrint("MakeComboBox")
      case "slider" => cPrint("MakeSlider")
      case "" => cPrint("MakeSpacer")
      case _ => throw new AssertionError()
    }

    cPrintln("(",prefix,"_props",", ",prefix,"_width",", ",prefix,"_height",")")
    finishWidget()
  }

  override def visit(node: Comparision): Any = {
    node.left.printExprCode()
    cPrint (" == ")
    node.right.printExprCode()
  }

  override def visit(node: Condition): Any = {
    for ((cond,expr) <- node.conds) {
      cPrint("if (")
      cond.printExprCode()
      cPrintln(") {")
      expr.printExprCode()
      cPrint("} else ")
    }
    cPrintln("{")
    node.otherwise.printExprCode()
    cPrint("}")
  }

  override def visit(node: Conjuction): Any = {
    assert(! node.elems.isEmpty)
    cPrintList(node.elems, " && ", "")
  }

  val widgetWidths: MutableHashMap[Widget, Option[LinearExpansion]] = new MutableHashMap[Widget, Option[LinearExpansion]]
  val widgetHeights: MutableHashMap[Widget, Option[LinearExpansion]] = new MutableHashMap[Widget, Option[LinearExpansion]]
  
  override def visit(node: Container): Any = {
    startWidget()
    val prefix = getCurrentWidgetName
    var tmp = node.parent
    while (tmp != null && ! tmp.isInstanceOf[Container])
      tmp = tmp.parent

    cPrint("val ",prefix,"_width: Option[DSLProperty[Int]] = ")
    node.width match {
      case Some(realExpr) => cPrint("Some(new InitialProperty[Int]("); realExpr.printExprCode(); cPrint(", ", UpdateManagerName, "))")
      case None => cPrint("None")
    }
    cPrintln()
    cPrint("val ",prefix,"_height: Option[DSLProperty[Int]] = ")
    node.height match {
      case Some(realExpr) => cPrint("Some(new InitialProperty[Int]("); realExpr.printExprCode(); cPrint(", ", UpdateManagerName, "))")
      case None => cPrint("None")
    }
    cPrintln()

    // MultiSplitPane(val orientation: Orientation, val mspParent: MultiSplitPane = null, val um: UpdateManager, val width: Option[DSLProperty[Int]],  val height: Option[DSLProperty[Int]])
    cPrint("val ",prefix,": MultiSplitPane = new MultiSplitPane(")
    cPrint("Orientation.", if (node.isVertical) "VERTICAL" else "HORIZONTAL")
    cPrint(", ", if (tmp != null) getParentWidgetName else "null")
    cPrint(", ",prefix,"_width, ",prefix,"_height",")")

    /* This semicolon is essential, so that the following curly braces will not
     * be interpreted as an anonymous sub-class
     */
    cPrintln(";")

    cPrintln("{")
    startChildren()
    for (wdgt <- node.children) {
      wdgt accept this
      // Now, add the widget to it's parent
      cPrintln(prefix,".add(",getCurrentWidgetName,")")
    }
    finishChildren()
    cPrintln("}")

    cPrintln(prefix,".callAfterAddingLastChild()")
    cPrintln(prefix,".callAfterLastModification(", UpdateManagerName, ")")
    finishWidget()
  }

  override def visit(node: Disjunction): Any = {
    assert(! node.elems.isEmpty)
    cPrintList(node.elems, " || ", "")
  }

  override def visit(node: ExpressionAttribute): Any = {
    val newValName = "newValue"

    cPrint("val ",globalVarName(node.leftSide)," = new DefinedProperty[",node.leftSide.resultType,"]")
    cPrint("(",node.getLeftType.toCode,", ",UpdateManagerName)
    if (! wa.dependencies(node.leftSide).isEmpty) {
      cPrint(", ")
      cPrint((wa.dependencies(node.leftSide).toList map {(rv) => globalVarName(rv.getVariable)}).mkString(", "))
    }
    cPrintln(") {")

      var isReadOnly = false

      cPrintln("override def reCompute(): ",node.leftSide.resultType," = {")
      node.realValue.printExprCode(); cPrintln()
      cPrintln("}")

      cPrintln("override def requestUpdate(", newValName, ": ",node.leftSide.resultType,") {")
      node.leftSide.resultType match {
        /* Reversing an integer expression */
        case Type.tInt => {
          val (expr, writeStatus) = wa.integerExpansions(node.leftSide)
          /* Check if this is a reversible expression */
          if (writeStatus == ReadOnlyStatus.ReadAndWrite) {
            expr match {
              // y = a * x + b   =>   x = (y - b) / a
              case Some(linearExp) => assignToVar(linearExp.v) { cPrint("((",newValName," - ", linearExp.b, ") / ", linearExp.a, ".asInstanceOf[Double]).asInstanceOf[Int]") }
              case _ => /* In write mode, we must have an expression so this shouldn't happen! */ assert(false)
            }
          } else {
            isReadOnly = true
          }
        }

        /* Reversing a boolean expression */
        case Type.tBoolean => {
          val (expr, writeStatus) = wa.booleanExpansions(node.leftSide)
          if (writeStatus == ReadOnlyStatus.ReadAndWrite) {
            expr match {
              case Some(boolExp) => {
                cPrintln ("if (",newValName,") {")
                  if (boolExp.canAssignTrue) {
                    for ((vari, value) <- boolExp.getAssignmentsForTrue) {
                      assignToVar(vari) { value accept this }; cPrintln()
                    }
                  } else {
                    //cPrintln("throw new UnsupportedOperationException()")
                  }
                cPrintln ("} else {")
                  if (boolExp.canAssignFalse) {
                    for ((vari, value) <- boolExp.getAssignmentsForFalse) {
                      assignToVar(vari) { value accept this }; cPrintln()
                    }
                  } else {
                    //cPrintln("throw new UnsupportedOperationException()")
                  }
                cPrint ("}")
              }
              case _ => /* In write mode, we must have an expression so this shouldn't happen! */ assert(false)
            }
          } else {
            isReadOnly = true
          }
        }

        /* Reversing an expression of a different type */
        case _ => {
          val freeVar = wa.otherExpansions(ResolvedVariable from node.leftSide)
          freeVar match {
            case Some(fv) => assignToVar(fv) { cPrint(newValName) }
            /* No way to update */
            case _ => isReadOnly = true
          }
        }
      } /* End of matching on the type of the reverse expression */

      if (isReadOnly)
        cPrint("throw new UnsupportedOperationException()")

    cPrintln()
    cPrintln(" }")

    cPrintln("def isReadOnly: Boolean = ", isReadOnly)

    cPrintln("}")
  }

  override def visit(node: FunctionCall): Any = {
    node.function.printExprCode()
    cPrint("(")
    cPrintList(node.args, ", ", "")
    cPrint(")")
  }

  override def visit(node: InitialAttribute): Any = {
    cPrint("val ",globalVarName(node.leftSide)," = new InitialProperty[",node.leftSide.resultType,"](")
    cPrint(inputPropsName,".getOrElse(\"",node.leftSide.id,"\",")
    node.value match {
      case Some(value) => value.printExprCode()
      case None => cPrint("throw new Exception(\"Expected an input value for ",node.leftSide.id,"\")")
    }
    cPrintln(").asInstanceOf[",node.leftSide.resultType,"], ",UpdateManagerName,")")
  }

  override def visit(node: IterationVariable): Any = {
    throw new AssertionError()
  }

  override def visit[T](node: Literal[T]): Any = {
    node match {
      case Literal(number:Int) => {
        cPrint(Integer toString number)
      }
      case Literal(bool:Boolean) => {
        cPrint(if (bool) "true" else "false")
      }
      case Literal(string:String) => {
        cPrintStringLiteral(string)
      }
      case Literal(expressions.Color(hexa)) => {
        cPrint("new Color(");cPrintStringLiteral(hexa);cPrint(")")
      }
      case Literal(expressions.Font(face,size,style)) => {
        cPrint("new Font(")
        cPrintStringLiteral(face)
        cPrint(",",size,",","TextStyle.",style.name,")")
      }
      case Literal(h:expressions.HAlign) => {
        cPrint("HAlign.", h.name)
      }
      case Literal(v:expressions.VAlign) => {
        cPrint("VAlign.", v.name)
      }
      case _   => throw new AssertionError()
    }
  }

  override def visit(node: Negation): Any = {
    cPrint("! ")
    node.expr.printExprCode()
  }

  override def visit(node: Product): Any = {
    assert(! (node.mul.isEmpty && node.div.isEmpty ))
    cPrintList(node.mul, " * ", "1")
    if (! node.div.isEmpty) {
      cPrint("/ (")
      cPrintList(node.div, " * ", "")
      cPrint(")")
    }
  }

  override def visit(node: Program): Any = {
    null
  }

  override def visit(node: PropertyScope): Any = {
    node.widget accept this
  }

  override def visit(node: Sum): Any = {
    assert(! (node.add.isEmpty && node.sub.isEmpty ))
    cPrintList(node.add, " + ", "0")
    if (! node.sub.isEmpty) {
      cPrint("- (")
      cPrintList(node.sub, " + ", "")
      cPrint(")")
    }
  }

  override def visit(node: Variable): Any = {
    cPrint(globalVarName(node),".get")
  }

  def assignToVar(node: Variable)(code: => Unit) {
    cPrint(globalVarName(node),".requestUpdate(")
    code
    cPrint(")")
  }
}
